/* standard headers */
#include <linux/utsname.h>
#include <linux/path.h>
#include <linux/vmalloc.h>

/* custom headers */
#include "ak_common.h"
#include "lsm_public.h"

#ifdef __64bit__
//clear WP bit of CR0, and return the original value
unsigned long  clear_and_return_cr0(void)
{
    unsigned long cr0 = 0;
    unsigned long ret;
    asm volatile ("movq %%cr0, %%rax"
            : "=a"(cr0)
            );  
    ret = cr0;
    /* clear the 20 bit of CR0, a.k.a WP bit */
    cr0 &= 0xfffeffff;
    asm volatile ("movq %%rax, %%cr0"
            :   
            : "a"(cr0)
            );  
    return ret;
}
/** set CR0 with new value
 * @val : new value to set in cr0
 */
//读取val的值到rax寄存器，再将rax寄存器的值放入cr0中
void  setback_cr0(unsigned long val)
{
    asm volatile ("movq %%rax, %%cr0"
            :   
            : "a"(val)
            );  
}

#else
//clear WP bit of CR0, and return the original value
unsigned long  clear_and_return_cr0(void)
{
    unsigned long cr0 = 0;
    unsigned long ret;
    asm volatile ("movl %%cr0, %%eax"
            : "=a"(cr0)
            );  
    ret = cr0;
    /* clear the 20 bit of CR0, a.k.a WP bit */
    cr0 &= 0xfffeffff;
    asm volatile ("movl %%eax, %%cr0"
            :   
            : "a"(cr0)
            );  
    return ret;
}
/** set CR0 with new value
 * @val : new value to set in cr0
 */
//读取val的值到eax寄存器，再将eax寄存器的值放入cr0中
void setback_cr0(unsigned long val)
{
    asm volatile ("movl %%eax, %%cr0"
            :   
            : "a"(val)
            );  
}
#endif//__64Bit__
#if 1
unsigned long find_symbol_addr(char *symname)
{
    int len;
    unsigned long ret = 0x0000UL;
    struct file *fp = NULL;
    char *p,*mem = NULL;
    int size,linelen;
    char *linep;
    loff_t back_pos;
    mm_segment_t oldfs;
    int namelen = 0;

    char path[128] = {0};
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16)
    struct new_utsname *uts =&system_utsname;
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
    struct new_utsname *uts = init_utsname();
#else
    struct new_utsname *uts = utsname();
#endif
    sprintf(path, "/boot/System.map-%s",uts->release);
    printk("path[%s]\n",path);
    namelen = strlen(symname);
    oldfs = get_fs();
    set_fs(KERNEL_DS);
    fp = filp_open(path, O_RDONLY, 0);
    if(IS_ERR(fp)){
        printk("open file error\n");
        goto end;
    }
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18)
    size = fp->f_path.dentry->d_inode->i_size;
#else
    size = fp->f_dentry->d_inode->i_size;
#endif

    if(size == 0){
        goto end;
    }
    back_pos = fp->f_pos;
    fp->f_pos = 0;
    mem = vmalloc(size);
    if(mem == NULL){
        printk("kmalloc %d\n",size);
        goto end;
    }
    len = vfs_read(fp, mem, size, &fp->f_pos);
    if(len != size){
        goto end;
    }
    for_each_line(mem, size, '\n', linep, linelen){
#if 1
        p = linep;
        strsep(&p," ");
        strsep(&p," ");
        if((memcmp(p, symname, namelen) == 0)&&((p[namelen]==' ')||(p[namelen]=='\r')||(p[namelen]=='\n')||(p[namelen]=='\0'))){
            ret = simple_strtol(linep,NULL,16);
            goto end;
        }   
#endif
    }
end:    
    set_fs(oldfs);
    if(mem){
        vfree(mem);
        mem = NULL;
    }
    if(fp){
        filp_close(fp, 0);
    }
    return ret;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
static int find_symbol_callback(struct kernsym *sym, const char *name, struct module *mod,
        unsigned long addr) {

    if (sym->found) {
        sym->end_addr = (unsigned long *)addr;
        return 1;
    }

    // this symbol was found. the next callback will be the address of the next symbol
    if (name && sym->name && !strcmp(name, sym->name)) {
        sym->addr = (unsigned long *)addr;
        sym->found = true;
    }

    return 0;
}

// find this symbol
//end_addr is origal function
//addr is new function
//size is distance function addresses
int find_symbol_address(struct kernsym *sym, const char *symbol_name) {

    int ret;

    sym->name = (char *)symbol_name;
    sym->found = 0;

    ret = kallsyms_on_each_symbol((void *)find_symbol_callback, sym);

    if (!ret)
        return -EFAULT;

    sym->size = sym->end_addr - sym->addr;
    sym->new_size = sym->size;
    sym->run = sym->addr;

    return 0;
}

#else

/*

   We resort to using your /proc/kallsyms and System.map files since there really
   is no other (easy) way. I could try to brute force the kernel memory range to
   find the kallsyms_addresses table, and maybe one day I'll try that. But not
   today.

 */

#define SYSTEM_MAP_PATH "/boot/System.map-"

// borrowed (copied) from simple_strtol() in vsprintf.c

unsigned long str2long(const char *cp, char **endp, unsigned int base) {
    if (*cp == '-')
        return -simple_strtoull(cp + 1, endp, base);
    return simple_strtoull(cp, endp, base);
}

// look up the symbol address from a file. used as the last method to try
// borrowed from memset's blog (with some needed modifications):
// http://memset.wordpress.com/2011/01/20/syscall-hijacking-dynamically-obtain-syscall-table-address-kernel-2-6-x/

int find_symbol_address_from_file(struct kernsym *sym, const char *filename) {

    char buf[MAX_FILE_LEN];
    int i = 0;
    int ret = -EFAULT;
    char *p, *substr;
    struct file *f;

    mm_segment_t oldfs;

    oldfs = get_fs();
    set_fs (KERNEL_DS);

    f = filp_open(filename, O_RDONLY, 0);

    if (IS_ERR(f)) {
        ret = PTR_ERR(f);
        printk("Unable to open file %s\n", filename);
        goto out_nofilp;
    }

    memset(buf, 0x0, MAX_FILE_LEN);

    p = buf;

    while (vfs_read(f, p+i, 1, &f->f_pos) == 1) {

        if (p[i] == '\n' || i == (MAX_FILE_LEN-1)) {

            char *sys_string;

            // symbol was found, next symbols is the end address
            if (sym->found) {

                sys_string = kmalloc(MAX_FILE_LEN, GFP_KERNEL);
                if (sys_string == NULL) {
                    ret = -ENOMEM;
                    goto out;
                }

                memset(sys_string, 0, MAX_FILE_LEN);
                strncpy(sys_string, strsep(&p, " "), MAX_FILE_LEN);
                sym->end_addr = (unsigned long *) str2long(sys_string, NULL, 16);

                kfree(sys_string);
                sys_string = NULL;

                sym->size = sym->end_addr - sym->addr;
                sym->new_size = sym->size;
                ret = 0;
                goto out;
            }

            i = 0;

            substr = strstr(p, sym->name);

            if (!sym->found && substr != NULL && substr[-1] == ' ' && ((substr[strlen(sym->name)+1] == '\0') || (substr[strlen(sym->name)+1] == '['))) {

                sys_string = kmalloc(MAX_FILE_LEN, GFP_KERNEL); 

                if (sys_string == NULL) {
                    ret = -ENOMEM;
                    goto out;
                }

                memset(sys_string, 0, MAX_FILE_LEN);
                strncpy(sys_string, strsep(&p, " "), MAX_FILE_LEN);

                sym->addr = (unsigned long *) str2long(sys_string, NULL, 16);
                kfree(sys_string);
                sys_string = NULL;

                sym->found = true;
            }

            memset(buf, 0x0, MAX_FILE_LEN);
            continue;
        }

        i++;

    }

out:

    filp_close(f, 0);

out_nofilp:

    set_fs(oldfs);

    return ret;
}

// look everywhere on the system that might contain the addresses we want

int find_symbol_address(struct kernsym *sym, const char *symbol_name) {

    char *filename;
    int ret;

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 16)
    struct new_utsname *uts =&system_utsname;
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
    struct new_utsname *uts = init_utsname();
#else
    struct new_utsname *uts = utsname();
#endif

    sym->name = symbol_name;

    ret = find_symbol_address_from_file(sym, "/proc/kallsyms");

    if (IN_ERR(ret)) {
        filename = kmalloc(strlen(uts->release)+strlen(SYSTEM_MAP_PATH)+1, GFP_KERNEL);
        if (filename == NULL) {
            ret = -ENOMEM;
            goto out;
        }
        memset(filename, 0, strlen(SYSTEM_MAP_PATH)+strlen(uts->release)+1);

        strncpy(filename, SYSTEM_MAP_PATH, strlen(SYSTEM_MAP_PATH));
        strncat(filename, uts->release, strlen(uts->release));
        ret = find_symbol_address_from_file(sym, filename);
        kfree(filename);
        filename = NULL;
    }
    sym->run = sym->addr;

out:
    if (IN_ERR(ret))
        printk("Failed to find symbol address for %s\n", symbol_name);
    return ret;
}

#endif
#endif
